﻿using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace QQ2564874169.Core.Caching
{
    public class AppCache : ICache
    {
        private ConcurrentDictionary<string, _CacheItem> _cache;

        public AppCache()
        {
            _cache = new ConcurrentDictionary<string, _CacheItem>();

            Task.Factory.StartNew(ClearTimeoutItem);
        }

        private void ClearTimeoutItem()
        {
            while (_cache != null)
            {
                Parallel.ForEach(_cache.Keys.ToArray(), key =>
                {
                    _CacheItem item;
                    if (_cache.TryGetValue(key, out item) == false)
                        return;

                    var data = item.Data;
                    if (data.AbsoluteExpiration.HasValue &&
                        item.CreateTime.AddSeconds(data.AbsoluteExpiration.Value) < DateTime.Now)
                    {
                        _cache.TryRemove(key, out item);
                    }
                    else if (data.SlidingExpiration.HasValue)
                    {
                        if (item.GetTime.AddSeconds(data.SlidingExpiration.Value) < DateTime.Now)
                        {
                            _cache.TryRemove(key, out item);
                        }
                    }
                });
                Thread.Sleep(1000 * 60 * 5);
            }
        }

        public void Save(CacheItem data)
        {
            var item = new _CacheItem(data)
            {
                CreateTime = DateTime.Now,
                GetTime = DateTime.Now
            };
            _cache.AddOrUpdate(data.Key, item, (k, i) => item);
        }

        public void Update(string key, object value)
        {
            _CacheItem item;
            if (_cache.TryGetValue(key, out item))
            {
                if (item.Data.SlidingExpiration.HasValue)
                    item.GetTime = DateTime.Now;
                item.Data.Value = value;
            }
        }

        public bool Exists(string key)
        {
            _CacheItem item;
            if (_cache.TryGetValue(key, out item))
            {
                var data = item.Data;
                if (data.AbsoluteExpiration.HasValue &&
                    item.CreateTime.AddSeconds(data.AbsoluteExpiration.Value) < DateTime.Now)
                {
                    _cache.TryRemove(key, out item);
                    return false;
                }
                if (data.SlidingExpiration.HasValue)
                {
                    if (item.GetTime.AddSeconds(data.SlidingExpiration.Value) < DateTime.Now)
                    {
                        _cache.TryRemove(key, out item);
                        return false;
                    }
                }
                return true;
            }
            return false;
        }

        public object Get(string key)
        {
            if (_cache.ContainsKey(key) == false)
                return null;
            _CacheItem item;
            if (_cache.TryGetValue(key, out item) == false)
                return null;
            var data = item.Data;
            if (data.AbsoluteExpiration.HasValue &&
                item.CreateTime.AddSeconds(data.AbsoluteExpiration.Value) < DateTime.Now)
            {
                _cache.TryRemove(key, out item);
                return null;
            }
            if (data.SlidingExpiration.HasValue)
            {
                if (item.GetTime.AddSeconds(data.SlidingExpiration.Value) < DateTime.Now)
                {
                    _cache.TryRemove(key, out item);
                    return null;
                }
                item.GetTime = DateTime.Now;
            }
            return data.Value;
        }

        public void Remove(string key)
        {
            _CacheItem item;
            _cache.TryRemove(key, out item);
        }

        private class _CacheItem
        {
            public DateTime CreateTime { get; set; }
            public DateTime GetTime { get; set; }
            public CacheItem Data { get; }

            public _CacheItem(CacheItem data)
            {
                Data = data;
            }
        }
    }
}
